﻿namespace Sina.Weibo
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Threading.Tasks;
    using System.Web;

    /// <summary>
    /// This class is designed to help web applications developers handle user authentication/authorization process.
    /// </summary>
    public class WeiboAuthClient : INotifyPropertyChanged
    {
        private readonly WeiboAuthClientCore authClient;
        private readonly string defaultRedirectUrl;
        private WeiboConnectSession session;
        private string currentUserId;
        private bool sessionChanged;
        private bool currentUserIdChanged;

        /// <summary>
        /// Initializes an instance of WeiboAuthClient class.
        /// </summary>
        /// <param name="clientId">The client Id of the app.</param>
        /// <param name="clientSecret">The client secret of the app.</param>
        /// <param name="defaultRedirectUrl">The default redirect URL for the site.</param>
        public WeiboAuthClient(
            string clientId, 
            string clientSecret, 
            string defaultRedirectUrl)
        {
            WeiboUtility.ValidateNotNullOrWhiteSpaceString(clientId, "clientId");
            WeiboUtility.ValidateNotNullOrWhiteSpaceString(clientSecret, "clientSecret");
            if (!string.IsNullOrWhiteSpace(defaultRedirectUrl))
            {
                WeiboUtility.ValidateUrl(defaultRedirectUrl, "defaultRedirectUrl");
                this.defaultRedirectUrl = defaultRedirectUrl;
            }

            this.authClient = new WeiboAuthClientCore(clientId, clientSecret, this);
        }

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Gets the current session.
        /// </summary>
        public WeiboConnectSession Session
        {
            get
            {
                return this.session;
            }

            internal set
            {
                if (this.session != value)
                {
                    this.session = value;
                    this.UpdateCurrentUserId();
                    this.sessionChanged = true;
                }
            }
        }

        /// <summary>
        /// Gets the current user's Id.
        /// </summary>
        public string CurrentUserId
        {
            get
            {
                return this.currentUserId;
            }

            private set
            {
                if (this.currentUserId != value)
                {
                    this.currentUserId = value;
                    this.currentUserIdChanged = true;
                }
            }
        }

        /// <summary>
        /// Exchange authentication code from the Http request for access token.
        /// </summary>
        /// <param name="context">The HttpContextBase instance.</param>
        /// <returns>An async Task instance.</returns>
        public Task<WeiboLoginResult> ExchangeAuthCodeAsync(HttpContextBase context)
        {
            return this.ExchangeAuthCodeAsync(context, this.defaultRedirectUrl);
        }

        /// <summary>
        /// Exchange authentication code from the Http request for access token.
        /// </summary>
        /// <param name="context">The HttpContextBase instance.</param>
        /// <param name="redirectUrl">The redirect URL of the app.</param>
        /// <returns>An async Task instance.</returns>
        public Task<WeiboLoginResult> ExchangeAuthCodeAsync(HttpContextBase context, string redirectUrl)
        {
            WeiboUtility.ValidateNotNullParameter(context, "context");
            WeiboUtility.ValidateUrl(redirectUrl, "redirectUrl");

            return this.authClient.ExchangeAuthCodeAsync(redirectUrl, context);
        }

        /// <summary>
        /// Generates a consent URL that includes a set of provided parameters.
        /// </summary>
        /// <param name="scopes">A list of scope values that the user will need to consent to.</param>
        /// <param name="redirectUrl">The URL that the page will be redirected to after authorize process is completed.</param>
        /// <param name="options">Optional query string parameters.</param>
        /// <returns>The generated login URL value.</returns>
        public string GetLoginUrl(IEnumerable<string> scopes, string redirectUrl, IDictionary<string, string> options)
        {
            WeiboUtility.ValidateNotEmptyStringEnumeratorArguement(scopes, "scopes");
            WeiboUtility.ValidateUrl(redirectUrl, "redirectUrl");

            string state = null;
            string language = null;
            bool forceLogin = false;
            DisplayType display = DisplayType.Default;

            if (options != null)
            {
                if (options.ContainsKey(AuthConstants.State))
                {
                    state = options[AuthConstants.State];
                }

                if (options.ContainsKey(AuthConstants.Display))
                {
                    string displayStr = options[AuthConstants.Display];
                    if (!Enum.TryParse<DisplayType>(displayStr, true, out display))
                    {
                        throw new ArgumentException(ErrorText.ParameterInvalidDisplayValue, "display");
                    }
                }

                if (options.ContainsKey(AuthConstants.ForceLogin))
                {
                    string forceLoginStr = options[AuthConstants.ForceLogin];
                    if (!bool.TryParse(forceLoginStr, out forceLogin))
                    {
                        throw new ArgumentException(ErrorText.ParameterInvalidForceLoginValue, "forcelogin");
                    }
                }

                if (options.ContainsKey(AuthConstants.Language))
                {
                    language = options[AuthConstants.Language];
                }
            }

            return this.authClient.GetLoginUrl(redirectUrl, scopes, state, display, forceLogin, language);
        }

        /// <summary>
        /// This method is used to ensure that the property changed event is only invoked once during the execution of
        /// InitUserPresentAsync and InitUserAbsentAsync methods.
        /// </summary>
        internal void FirePendingPropertyChangedEvents()
        {
            if (this.sessionChanged)
            {
                this.OnPropertyChanged("Session");
                this.sessionChanged = false;
            }

            if (this.currentUserIdChanged)
            {
                this.OnPropertyChanged("CurrentUserId");
                this.currentUserIdChanged = false;
            }
        }

        private void UpdateCurrentUserId()
        {
            string userId = null;
            if (this.session != null)
            {
                string sessionUserId = this.session.UserId;
                if (!string.IsNullOrEmpty(sessionUserId))
                {
                    userId = sessionUserId;
                }
            }

            this.CurrentUserId = userId;
        }

        private void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

    }
}
